package sf.querydsl;

import com.querydsl.core.QueryMetadata;
import com.querydsl.core.QueryModifiers;
import com.querydsl.core.Tuple;
import com.querydsl.core.support.QueryBaseVistor;
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.Path;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.Wildcard;
import com.querydsl.sql.AbstractSQLQuery;
import com.querydsl.sql.ColumnMetadata;
import com.querydsl.sql.Configuration;
import com.querydsl.sql.PrimaryKey;
import com.querydsl.sql.RelationalPath;
import com.querydsl.sql.SQLBindings;
import com.querydsl.sql.SQLQuery;
import com.querydsl.sql.SQLTemplates;
import com.querydsl.sql.dml.AbstractSQLClause;
import com.querydsl.sql.dml.QueryDSLDMLVistor;
import com.querydsl.sql.dml.SQLDeleteClause;
import com.querydsl.sql.dml.SQLInsertClause;
import com.querydsl.sql.dml.SQLMergeClause;
import com.querydsl.sql.dml.SQLUpdateClause;
import sf.common.wrapper.Page;
import sf.core.DBObject;
import sf.database.dao.DBContext;
import sf.database.jdbc.sql.Crud;
import sf.database.jdbc.sql.OrmIterator;
import sf.database.jdbc.sql.OrmStream;
import sf.database.util.DBUtils;
import sf.spring.util.CollectionUtils;
import sf.tools.ArrayUtils;

import java.sql.Connection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class QueryDSLOrmImpl implements QueryDSLInf {
    @Override
    public Configuration queryDSLConfiguration(Connection conn, Class<? extends DBObject>... tableClass) {
        SQLTemplates sqlTemplates = DBUtils.doGetDialect(conn, false).getQueryDslDialect();
        Configuration configuration = new Configuration(sqlTemplates);
        //注入自定义类型
        if (tableClass != null && tableClass.length > 0) {
            for (int i = 0; i < tableClass.length; i++) {
                Class<?> clz = tableClass[i];
                QueryDSL.registerType(clz, configuration);
            }
        }
        return configuration;
    }

    @Override
    public <T extends DBObject> boolean queryDSLExists(Connection conn, List<Predicate> predicates, Class<T> clz) {
        DBContext context = DBUtils.doGetDBContext(conn);
        SQLRelationalPath table = QueryDSLTables.relationalPathBase(context, clz);
        SQLQuery<T> query = new SQLQuery<>();
        query.select(Wildcard.count).from(table).where(predicates.toArray(new Predicate[predicates.size()])).limit(1);
        setTemplate(conn, query);
        SQLBindings sqlBindings = query.getSQL();
        String sql = sqlBindings.getSQL();
        List<Object> values = sqlBindings.getNullFriendlyBindings();
        return Crud.getInstance().getCrudSql().selectOne(conn, Boolean.class, sql, values.toArray());
    }

    @Override
    public <T> List<T> queryDSLSelectList(Connection conn, AbstractSQLQuery<?, ?> query, Class<T> returnClass) {
        setTemplate(conn, query);
        SQLBindings sqlBindings = query.getSQL();
        String sql = sqlBindings.getSQL();
        List<Object> values = sqlBindings.getNullFriendlyBindings();
        Class<T> clz = returnClass;
        if (clz == null) {
            clz = getReturnClz(query);
        }
        return Crud.getInstance().getCrudSql().selectList(conn, clz, sql, values.toArray());
    }

    @Override
    public <T> Page<T> queryDSLSelectPage(Connection conn, AbstractSQLQuery<?, ?> query, Class<T> returnClass, long start, int limit) {
        setTemplate(conn, query);
        SQLBindings sqlBindings = query.getSQL();
        String countSql = QueryDSL.getCountSql(query);

        List<Object> countValues = sqlBindings.getNullFriendlyBindings();
        Object[] countValuesArr = null;
        if (countValues != null) {
            countValuesArr = countValues.toArray();
        } else {
            countValuesArr = ArrayUtils.EMPTY_OBJECT_ARRAY;
        }
        //sqlserver2000 不能设置offset
        if (start >= 0) {
            query.offset(start);
        } else {
            start = 0;
        }
        if (limit >= 0) {
            query.limit(limit);
        } else {
            limit = 0;
        }
        SQLBindings pageListSqlBindings = query.getSQL();
        String pageListSql = pageListSqlBindings.getSQL();
        List<Object> pageListValues = pageListSqlBindings.getNullFriendlyBindings();
        Object[] pageListValuesArr = null;
        if (pageListValues != null) {
            pageListValuesArr = pageListValues.toArray();
        } else {
            pageListValuesArr = ArrayUtils.EMPTY_OBJECT_ARRAY;
        }

        //还原offset和limit
        QueryMetadata qm = QueryBaseVistor.getQueryMetadata(query);
        qm.setModifiers(QueryModifiers.EMPTY);

        Class<T> clz = returnClass;
        if (clz == null) {
            clz = getReturnClz(query);
        }
        return Crud.getInstance().getCrudSql().selectRealPageRaw(conn, start, limit, clz, countSql, countValuesArr, pageListSql, pageListValuesArr);
    }


    @Override
    public <T> T queryDSLSelectOne(Connection conn, AbstractSQLQuery<?, ?> query, Class<T> returnClass) {
        setTemplate(conn, query);
        SQLBindings sqlBindings = query.getSQL();
        String sql = sqlBindings.getSQL();
        List<Object> values = sqlBindings.getNullFriendlyBindings();
        Class<T> clz = returnClass;
        if (clz == null) {
            clz = getReturnClz(query);
        }
        return Crud.getInstance().getCrudSql().selectOne(conn, clz, sql, values.toArray());
    }

    @Override
    public <T> void selectIterator(Connection conn, OrmIterator<T> ormIt, AbstractSQLQuery<?, ?> query, Class<T> returnClass) {
        setTemplate(conn, query);
        SQLBindings sqlBindings = query.getSQL();
        String sql = sqlBindings.getSQL();
        List<Object> values = sqlBindings.getNullFriendlyBindings();
        Class<T> clz = returnClass;
        if (clz == null) {
            clz = getReturnClz(query);
        }
        Crud.getInstance().getCrudSql().selectIterator(conn, ormIt, clz, false, sql, values.toArray());
    }

    @Override
    public <T> void selectStream(Connection conn, OrmStream<T> ormStream, AbstractSQLQuery<?, ?> query, Class<T> returnClass) {
        setTemplate(conn, query);
        SQLBindings sqlBindings = query.getSQL();
        String sql = sqlBindings.getSQL();
        List<Object> values = sqlBindings.getNullFriendlyBindings();
        Class<T> clz = returnClass;
        if (clz == null) {
            clz = getReturnClz(query);
        }
        Crud.getInstance().getCrudSql().selectStream(conn, ormStream, clz, false, sql, values.toArray());
    }

    @Override
    public int queryDSLInsert(Connection conn, SQLInsertClause insert) {
        RelationalPath base = QueryDSLDMLVistor.getRelationalPath(insert);
        return queryDSLSQLClause(conn, base, insert, null);
    }

    @Override
    public int queryDSLInsert(Connection conn, SQLInsertClause insert, List<Map<String, Object>> keyValues) {
        RelationalPath base = QueryDSLDMLVistor.getRelationalPath(insert);
        return queryDSLSQLClause(conn, base, insert, keyValues);
    }

    @Override
    public int queryDSLUpdate(Connection conn, SQLUpdateClause update) {
        RelationalPath base = QueryDSLDMLVistor.getRelationalPath(update);
        return queryDSLSQLClause(conn, base, update, null);
    }

    @Override
    public int queryDSLDelete(Connection conn, SQLDeleteClause delete) {
        RelationalPath base = QueryDSLDMLVistor.getRelationalPath(delete);
        return queryDSLSQLClause(conn, base, delete, null);
    }

    @Override
    public int queryDSLMerge(Connection conn, SQLMergeClause merge) {
        RelationalPath base = QueryDSLDMLVistor.getRelationalPath(merge);
        return queryDSLSQLClause(conn, base, merge, null);
    }

    @Override
    public int queryDSLMerge(Connection conn, SQLMergeClause merge, List<Map<String, Object>> keyValues) {
        RelationalPath base = QueryDSLDMLVistor.getRelationalPath(merge);
        return queryDSLSQLClause(conn, base, merge, keyValues);
    }

    private int queryDSLSQLClause(Connection conn, RelationalPath<?> base, AbstractSQLClause<?> dml, List<Map<String, Object>> keyValues) {
        setTemplate(conn, dml);
        List<String> pKeys = new ArrayList<>();
//        if (SQLRelationalPath.class.isAssignableFrom(base.getClass())) {
//            SQLRelationalPath path = (SQLRelationalPath) base;
//        }

        PrimaryKey pk = base.getPrimaryKey();
        if (pk != null) {
            List<Path<?>> list = pk.getLocalColumns();
            if (CollectionUtils.isNotEmpty(list)) {
                for (Path<?> p : list) {
                    ColumnMetadata cmd = base.getMetadata(p);
                    if (cmd != null) {
                        pKeys.add(cmd.getName());
                    }
                }
            }
        }

        List<SQLBindings> sqlBindings = dml.getSQL();
        Map<String, List<Object[]>> map = new HashMap<>();
        for (SQLBindings b : sqlBindings) {
            String sql = b.getSQL();
            List<Object[]> objects = map.get(sql);
            if (objects == null) {
                objects = new ArrayList<>();
                map.put(sql, objects);
            }
            objects.add(b.getBindings().toArray());
        }
        int count = 0;
        if (!map.isEmpty()) {
            for (Map.Entry<String, List<Object[]>> entry : map.entrySet()) {
                String sql = entry.getKey();
                List<Object[]> objects = entry.getValue();
                int[] counts = Crud.getInstance().getCrudSql().executeBatch(conn, sql, objects, false, 100, pKeys, keyValues);
                for (int i : counts) {
                    count += i;
                }
            }
        }
        return count;
    }

    /**
     * 设置sql模板
     * @param conn
     * @param query
     */
    private void setTemplate(Connection conn, AbstractSQLQuery<?, ?> query) {
        Configuration c = QueryDSL.getConfigurationQuery(query);
        if (c.getTemplates() == SQLTemplates.DEFAULT || c.getTemplates() == null) {
            SQLTemplates sqlTemplates = DBUtils.doGetDialect(conn, false).getQueryDslDialect();
            if (sqlTemplates != null) {
                c.setTemplates(sqlTemplates);
            } else {
                c.setTemplates(SQLTemplates.DEFAULT);
            }
        }
    }

    private void setTemplate(Connection conn, AbstractSQLClause<?> query) {
        Configuration c = QueryDSL.getConfigurationDML(query);
        if (c.getTemplates() == SQLTemplates.DEFAULT || c.getTemplates() == null) {
            SQLTemplates sqlTemplates = DBUtils.doGetDialect(conn, false).getQueryDslDialect();
            if (sqlTemplates != null) {
                c.setTemplates(sqlTemplates);
            } else {
                c.setTemplates(SQLTemplates.DEFAULT);
            }
        }
    }

    /**
     * 获取返回值的对象,该方法存在bug,在某些特殊方法下存在,返回值为空的情况
     * @param query
     * @param <T>
     * @return
     */
    private <T> Class<T> getReturnClz(AbstractSQLQuery<?, ?> query) {
        //在某些特殊方法下存在,expression为空
        //使用查询全部字段存在该问题(XXX.all())
        Expression<?> expression = query.getMetadata().getProjection();
        Class<?> type = expression.getType();
        Class<?> clz = Map.class;
        if (type != null && !Tuple.class.isAssignableFrom(type)) {
            clz = type;
        }
        return (Class<T>) clz;
    }
}
